﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using ExpressionEvaluator;
using ExpressionEvaluator.Expression;
using Calculator.Expression.Binary;
using Calculator.Expression.Unary;
using Calculator.Expression.Atom;

namespace Calculator
{
    public class Eval
    {
        private static readonly List<Expr> lstIdentifier = new List<Expr>();
        public double Evaluate(string expression)
        {
            token = new Token(expression);
            Expr result = Statement();
            return result.Value;
        }

        private Token token;
        public Expr Statement()
        {
            Expr expr = Assignment();

            if (token.Peek() != string.Empty)
                throw new Exception(token.GetParsedPart());

            return expr;
        }

        public Expr Command()
        {
            return null;
        }

        public Expr Assignment()
        {
            string st = token.Peek();
            if (st == "=")
            {
                token.NextToken();
            }
            return Expression();
        }

        public Expr Expression()
        {
            Expr sum = Term();
            string st = token.Peek();
            while (st == "+" || st == "-")
            {
                st = token.NextToken();
                if (st == "+")
                    sum = new Addition(sum, Term());
                else
                    sum = new Subtraction(sum, Term());
                st = token.Peek();
            }
            return sum;
        }
        public Expr Term()
        {
            Expr fac = Factor();
            string st = token.Peek();
            while (st == "*" || st == "/")
            {
                st = token.NextToken();
                if (st == "*")
                    fac = new Multiplication(fac, Factor());
                else
                    fac = new Divition(fac, Factor());
                st = token.Peek();
            }
            return fac;
        }

        public Expr Factor()
        {
            Expr fac = Primary();
            string st = token.Peek();

            if (st == "!" && fac.Value.ToString().RightOf(",").Length > 0)
                throw new Exception("Factor opration are only possible for integers.");

            while (st == "!")
            {
                st = token.NextToken();
                if (st == "!")
                    fac = new Factorial(fac);
                st = token.Peek();
            }

            while (st == "'")
            {
                st = token.NextToken();
                if (st == "'")
                    fac = new Differentiate(fac);
                st = token.Peek();
            }

            return fac;
        }

        public Expr Primary()
        {
            string st = token.Peek();
            if (st == "(")
            {
                token.NextToken();
                Expr ass = Assignment();
                st = token.NextToken(); // remove ")"
                if (st != ")")
                    throw new Exception(token.GetParsedPart());
                return ass;
            }
            if (st.ContainsAny(new List<string> { "cos", "sin", "log", "exp", "ln", "tan", "abs", "-" }))
            {
                st = token.NextToken();
                switch (st.ToLower())
                {
                    case "cos":
                        {
                            Expr ass = new Cos(Primary());
                            return ass;
                        }
                    case "sin":
                        {
                            Expr ass = new Sin(Primary());
                            return ass;
                        }
                    case "tan":
                        {
                            Expr ass = new Tan(Primary());
                            return ass;
                        }
                    case "abs":
                        {
                            Expr ass = new Abs(Primary());
                            return ass;
                        }
                    case "ln":
                        {
                            Expr ass = new Ln(Primary());
                            return ass;
                        }
                    case "log":
                        {
                            Expr ass = new Log(Primary());
                            return ass;
                        }
                    case "-":
                        {
                            Expr ass = new Negative(Primary());
                            return ass;
                        }
                }
            }
            else if (token.IsLetters)
            {
                st = token.NextToken();
                if (token.Peek() == "=")
                {
                    Expr expr = new Identifier(st, Assignment());
                    lstIdentifier.Remove(lstIdentifier.Find(x => x.Name == st));
                    lstIdentifier.Add(expr);
                    return expr;
                }
                return lstIdentifier.Exists(x => x.Name == st) ? lstIdentifier.Find(x => x.Name == st) : new Identifier(st);
            }
            else if (token.IsNumber)
            {
                double result = token.GetNumber();
                return new Constant(result);
            }
            throw new Exception(token.GetParsedPart());
        }
        public Expr Unary()
        {
            string st = token.Peek();
            Expr sum = Term();
            while (st == "+" || st == "-")
            {
                if (st == "+")
                    sum = new Addition(sum, Term());
                else
                    sum = new Subtraction(sum, Term());
            }
            return sum;
        }
    }
}
